package kernel;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import kernel.exception.OSException;

/**
 * Class responsible for disk operations regarding files
 * @author Jaromir Lepic, Petr Hajek
 *
 */
public class FileManager {

	/**
	 * Writes data string into file.
	 * @param data		Text to be written into file.
	 * @param path		The file path.
	 * @throws OSException 
	 */
	public static void writeData(String data, String path) throws OSException {
		File f = new File(path);
		writeData(data, f);
	}
	
	/**
	 * Writes data string into file.
	 * @param data		Text to be written into file.
	 * @param path		The file path.
	 * @throws OSException
	 */
	public static void writeData(String data, File path) throws OSException {
		PrintWriter pw = null;
		try {
			pw = new PrintWriter(new BufferedWriter(new FileWriter(path)));
			pw.println(data);
		} catch (IOException e) {
			throw new OSException(OSException.ERR_WRITING);
		} finally {
			if (pw != null) pw.close();
		}
	}
	
	/**
	 * Opens a reader on a specified file
	 * @param path Path to the file
	 * @return The reader instance
	 * @throws OSException
	 */
	public static BufferedReader openReader(File path) throws OSException{
		if (!exists(path)){
			throw new OSException(OSException.ERR_NOT_EXISTS);
		} else {
			try {
				return new BufferedReader(new FileReader(path));
			} catch (FileNotFoundException e) {
				throw new OSException(OSException.ERR_READING);
			}
		}
	}
	
	/**
	 * Opens writer to a specified file
	 * @param path Path to the file
	 * @param append If we should append the data
	 * @return The writer instance
	 * @throws OSException
	 */
	public static PrintWriter openWriter(File path, boolean append) throws OSException{
		try {
			return new PrintWriter(new BufferedWriter(new FileWriter(path, append)));
		} catch (IOException e) {
			throw new OSException(OSException.ERR_WRITING);
		}
	}
	
	
	/**
	 * Append data string to existing file.
	 * @param data		Text to be appended to file.
	 * @param path		The file path.
	 * @throws OSException 
	 */
	public static void appendData(String data, String path) throws OSException {
		File f = new File(path);
		appendData(data, f);
	}
	
	/**
	 * Append data string to existing file.
	 * @param data		Text to be appended to file.
	 * @param path		The file path.
	 * @throws OSException 
	 */
	public static void appendData(String data, File path) throws OSException {
		PrintWriter pw = null;
		try {
			pw = new PrintWriter(new BufferedWriter(new FileWriter(path, true)));
			pw.println(data);
		} catch (IOException e) {
			throw new OSException(OSException.ERR_WRITING);
		} finally {
			if (pw != null) pw.close();
		}
	}
	

	/**
	 * Reads data string from file.
	 * @param path		The file path.
	 * @return 	Text to be read from file.
	 * @throws OSException 
	 */
	public static String readData(String path) throws OSException {
		File f = new File(path);
		return readData(f);
	}
	
	public static String readData(File path) throws OSException {
		if (!path.isFile()) throw new OSException(OSException.ERR_NOT_EXISTS);
		String line = "";
		String read = "";
		BufferedReader br = null;
		try  {
			br = new BufferedReader(new FileReader(path));
			while ((line = br.readLine()) != null) {
				read += line + "\n";
			}
		} catch (IOException e) {
			throw new OSException(OSException.ERR_READING);
		} finally {
			if (br != null)
				try {
					br.close();
				} catch (IOException e) {
					throw new OSException(OSException.ERR_READING);
				}
		}
		return read;
	}
	

	/**
	 * Checks whether the file exists.
	 * @param path	 The file path.
	 * @return 'true' if the file exists, 'false' otherwise
	 * @throws OSException 
	 */
	public static boolean exists(String path) throws OSException {
		File f = new File(path);
		return exists(f);
	}
	
	/**
	 * Checks whether the file exists.
	 * @param path	 The file path.
	 * @return 'true' if the file exists, 'false' otherwise
	 * @throws OSException 
	 */
	public static boolean exists(File f) throws OSException {
		try {
			return f.exists();
		} catch (SecurityException e) {
			throw new OSException(OSException.ERR_ACCESS);
		}
	}
	
	/**
	 * Checks whether the directory exists.
	 * @param path	 The directory path.
	 * @return 'true' if the directory exists, 'false' otherwise
	 * @throws OSException
	 */
	public static boolean existsDir(String path) throws OSException {
		File f = new File(path);
		return existsDir(f);
	}
	
	/**
	 * Checks whether the directory exists.
	 * @param path	 The directory path.
	 * @return 'true' if the directory exists, 'false' otherwise
	 * @throws OSException
	 */
	public static boolean existsDir(File path) throws OSException {
		try {
			if (!path.isDirectory()) return false;
			else return path.exists();
		} catch (SecurityException e) {
			throw new OSException(OSException.ERR_ACCESS);
		}
	}
	
	/**
	 * Checks if the given path is absolute
	 * @param path Tested path
	 * @return True if absolute
	 */
	public static boolean isAbsolute(String path) {
		File f = new File(path);
		return isAbsolute(f);
	}
	
	/**
	 * Checks if the given path is absolute
	 * @param path Tested path
	 * @return True if absolute
	 */
	public static boolean isAbsolute(File path) {
		return path.isAbsolute();
	}

	/**
	 * Creates a new file.
	 * @param path 	The file path.
	 * @throws OSException 
	 */
	public static void createFile(String path) throws OSException {
		createFile(new File(path));
	}
	
	/**
	 * Creates a new file.
	 * @param path 	The file path.
	 * @throws OSException 
	 */
	public static void createFile(File path) throws OSException {
		if (path.exists()){
			throw new OSException(OSException.ERR_EXISTS);
		}
		try {
			boolean result = path.createNewFile();
			if (!result)
				throw new OSException(OSException.ERR_CREATING);
		} catch (IOException e) {
			throw new OSException(OSException.ERR_CREATING);
		}
	}
	
	/**
	 * Creates a directory at specified location, unless it exists
	 * @param path Path where the file will be created
	 * @return Name of the created file
	 * @throws OSException
	 */
	public static String createDir(String path) throws OSException {
		return createDir(new File(path));
	}
	
	/**
	 * Creates a directory at specified location, unless it exists
	 * @param path Path where the file will be created
	 * @return Name of the created file
	 * @throws OSException
	 */
	public static String createDir(File path) throws OSException {
		if (path.exists()){
			throw new OSException(OSException.ERR_EXISTS);
		}
		boolean result = path.mkdir();
		if (!result)
			throw new OSException(OSException.ERR_CREATING);
		else 
			return path.getName();
	}
	
	/**
	 * Creates a directory at specified location and all its necessary parents
	 * @param path Path where the file will be created
	 * @return Names of the created files
	 * @throws OSException
	 */
	public static List<String> createDirWithSubdirs(String path) throws OSException {
		return createDirWithSubdirs(new File(path));
	}
	
	/**
	 * Creates a directory at specified location and all its necessary parents
	 * @param path Path where the file will be created
	 * @return Names of the created files
	 * @throws OSException
	 */
	public static List<String> createDirWithSubdirs(File path) throws OSException {
		if (path.exists()){
			throw new OSException(OSException.ERR_EXISTS);
		}
		List<File> parents = getAllParents(path);
		List<String> names = new ArrayList<String>();
		for (File parent: parents){
			if (!parent.exists())
				names.add(0,parent.getName());
		}
		names.add(path.getName());
		boolean result = path.mkdirs();
		if (!result)
			throw new OSException(OSException.ERR_CREATING);
		else 
			return names;
		
	}

	/**
	 * Returns the parent directory path.
	 * @param path	The file path.
	 * @return The path of the parent.
	 * @throws OSException 
	 */
	public static String getParentDir(String path) {
		File f = new File(path);
		String par = f.getParent();
		if (par == null) return path;
		else return par;
	}
	
	/**
	 * Returns the parent directory path.
	 * @param path	The file path.
	 * @return The path of the parent.
	 * @throws OSException 
	 */
	public static File getParentDir(File f) {
		String par = f.getParent();
		if (par == null) return f;
		else {
			File out = new File(par);
			return out;
		}
	}
	
	/**
	 * Gets list of all parent files of the given file
	 * @param f Child file
	 * @return List of all parent files
	 */
	public static List<File> getAllParents(File f){
		List<File> parents = new ArrayList<File>();
		while ((f = f.getParentFile()) != null){
			parents.add(f);
		}
		return parents;
	}

	/**
	 * Returns the field containing files and directories located in given path.
	 * @param path		The directory path.
	 * @return	Field of strings.
	 * @throws OSException 
	 */
	public static String[] getDirContent(String path, boolean longInf) throws OSException {
		File f = new File(path);
		return getDirContent(f, longInf);
	}
	
	/**
	 * Returns the field containing files and directories located in given path.
	 * @param path		The directory path.
	 * @return	Field of strings.
	 * @throws OSException 
	 */
	public static String[] getDirContent(File path, boolean longInf) throws OSException {
		if (!path.exists()) throw new OSException(OSException.UNFOUND_PATH);
		if (path.isDirectory()) {
			if (!longInf) return path.list();
			else {
				File[] files = path.listFiles();
				String[] filelist = new String[files.length];
				for (int i = 0; i < files.length; i++) {
					Calendar cal = Calendar.getInstance();
					cal.setTimeInMillis(files[i].lastModified());
					DateFormat dt = new SimpleDateFormat("dd.MM.yyyy  HH:mm:ss");
					Date date = cal.getTime();
					if (files[i].isDirectory()) filelist[i] = dt.format(date) + "\t" + "\t" + files[i].getName();
					else filelist[i] = dt.format(date) + "\t" + files[i].length() + "B\t" + files[i].getName();
				}
				return filelist;
			}
		}
		else {
			throw new OSException(OSException.ERR_NOT_DIR);
		}
	}
	
	/**
	 * Returns the field containing files and directories corresponding to regular expression located in given path.
	 * @param path		The directory path.
	 * @param par		Regular expression.
	 * @param longInf	Whether the output is long (contains additional information about files) or short (contains just names of files).	
	 * @return	Field of strings.
	 * @throws OSException 
	 * @author Petr Hajek
	 */
	public static String[] getDirContentUsingRegex(File path, String par, boolean longInf) throws OSException {
		if (par.endsWith("\n")) par = par.substring(0, par.length() - 1);
		File f = new File(par);
		String newPath = "";
		String[] parsed = par.split("/");
		String regex = parsed[parsed.length - 1];
		regex = regex.replace(".", "\\.");
		regex = regex.replace("*", ".*");
		regex = regex.replace("?", ".");
		Pattern pat = Pattern.compile(regex);
		
		for (int i = 0; i < parsed.length - 1; i++) {
			newPath += parsed[i] + "/";
		}	
		
		if (!f.isAbsolute()) {
			newPath = getPath(newPath, path).toString();
		}	
		
		path = new File(newPath);
		File[] listDir = path.listFiles();
		if (listDir == null) throw new OSException(OSException.UNFOUND_PATH);
		ArrayList<File> matches = new ArrayList<File>(0);
		for (int i = 0; i < listDir.length; i++) {
			Matcher mat = pat.matcher(listDir[i].getName());
			if (mat.matches()) {
				matches.add(listDir[i]);
			}
		}
		if (!longInf) return matches.toArray(new String[matches.size()]);
		else {
			String[] filelist = new String[matches.size()];
			for (int i = 0; i < matches.size(); i++) {
				Calendar cal = Calendar.getInstance();
				cal.setTimeInMillis(matches.get(i).lastModified());
				DateFormat dt = new SimpleDateFormat("dd.MM.yyyy  HH:mm:ss");
				Date date = cal.getTime();
				if (matches.get(i).isDirectory()) filelist[i] = dt.format(date) + "\t" + "\t" + matches.get(i).getName();
				else filelist[i] = dt.format(date) + "\t" + matches.get(i).length() + "B\t" + matches.get(i).getName();
			}
			return filelist;
		}
	}
	
	
	/**
	 * Deletes the file defined by the path.
	 * @param path		The file path.
	 * @throws OSException 
	 */
	public static void deleteFile(String path) throws OSException {
		deleteFile(new File(path));
	}
	
	/**
	 * Deletes the file defined by the path.
	 * @param path		The file path.
	 * @throws OSException 
	 */
	public static void deleteFile(File f) throws OSException {
		try {
			if (exists(f)) {
				if (f.isFile()) {
					f.delete();
				}
				else {
					throw new OSException(OSException.ERR_NOT_FILE);
				}
			}
			else {
				throw new OSException(OSException.ERR_NOT_EXISTS);
			}
		} catch (SecurityException e) {
			throw new OSException(OSException.ERR_ACCESS);
		}
	}
	
	/**
	 * Gets a File denoting the path where program was launched. 
	 * If the File is not a directory (e.g. jar file) returns it's parent.
	 * @return The location where the program was launched
	 */
	public static File getStartingPath(){
		File location = new File(IOManager.class.getProtectionDomain().getCodeSource().getLocation().getPath());
		if (location.isFile())
			return location.getParentFile();
		else 
			return location;
	}
	
	/**
	 * Gets complete and canonical path to a file under a path
	 * @param path relative path to the file
	 * @param workingDir directory where relative path "starts"
	 * @return File denotig the canonical path
	 * @throws OSException
	 */
	public static File getPath(String path, File workingDir) throws OSException {
		if (path.endsWith("\n")) path = path.substring(0, path.length() - 1);
		File f = new File(path);
		String newDir = "";
		if (f.isAbsolute()) {
			return f;
		}
		else {
			if (path.startsWith("./")) {
				if (path.startsWith(".//")) throw new OSException(OSException.UNFOUND_PATH);
				try {
					newDir += URLDecoder.decode(workingDir.getAbsolutePath(), "UTF-8") + path.substring(1);
				} catch (UnsupportedEncodingException e) {
					throw new OSException("Unsupported encoding.");
				}
			}
			else if (path.equals("/")) {
				String root = workingDir.toString().substring(0, 3);
				File rootFile = new File(root);
				try {
					rootFile = rootFile.getCanonicalFile();
				} catch (IOException e) {
					throw new OSException(OSException.UNFOUND_PATH);
				}
				return rootFile;
			}
			else if (path.startsWith("/")) {
				String root = workingDir.toString().substring(0, 3);
				newDir = root + path.substring(1);
			}
			else {
				try {
					newDir += URLDecoder.decode(workingDir.getAbsolutePath(), "UTF-8") + "\\" + path;
				} catch (UnsupportedEncodingException e) {
					throw new OSException("Unsupported encoding.");
				}
			}
			File newD = new File(newDir);
			try {
				newDir = newD.getCanonicalPath();
			} catch (IOException e) {
				throw new OSException(OSException.UNFOUND_PATH);
			}
			f = new File(newDir);
			return f;
		}
	}

	/**
	 * Sets the timestamp of a file to now
	 * @param f File to be updated
	 */
	public static void actualizeTimestamp(File f) {
		f.setLastModified(System.currentTimeMillis());		
	}
}
